use async_trait::async_trait;
use std::fmt::Display;

use common_base::prelude::*;

use crate::{
    cluster::EaiServer,
    data::{DataObject, ParamObject, ResponseObject},
    service_engine::ServiceComponentManager,
};

use super::remote::InvokerClient;

#[async_trait]
pub trait Invoker: Sync {
    type InvokeReturn: TryIntoResponseObject + Send;

    async fn call_method(
        self,
        param_object: ParamObject,
        data_object: Option<DataObject>, /* custom_object, addon_object */
    ) -> Result<Self::InvokeReturn>;
}

// maybe this is not necessary
// in osp code, someone use iom to get dbconn, that means iom's response not only ResponseObject
// that's the reason try_into_ro exist. If we can make sure iom always return ResponseObject, we
// can drop this trait
pub trait TryIntoResponseObject {
    fn try_into_ro(&self) -> Result<ResponseObject>;
}

#[derive(Clone, Copy, common_base::enum_display_derive::Display)]
pub enum InvokerName {
    ServiceComponentInvoker,
}

impl TryFrom<&str> for InvokerName {
    type Error = ErrorCode;

    fn try_from(value: &str) -> std::result::Result<Self, Self::Error> {
        match value.trim() {
            "ServiceComponentInvoker" => Ok(InvokerName::ServiceComponentInvoker),
            _ => Err(ErrorCode::InvokerNotFound(format!(
                "can not find Invoker for {}",
                value
            ))),
        }
    }
}

/// maybe this is not necessary
/// see the comment in TryIntoResponseObject
// pub struct DbConnManagerInvoker;

// #[async_trait]
// impl Invoker for DbConnManagerInvoker {
//     type InvokeReturn = DbConnection;

//     async fn call_method(
//         &self,
//         mut param_object: ParamObject,
//         _data_object: Option<DataObject>, /* custom_object, addon_object */
//     ) -> Result<Self::InvokeReturn> {
//         DbConnManager::get_connection(&mut param_object).await
//     }
// }

pub struct ServiceComponentInvoker;

#[async_trait]
impl Invoker for ServiceComponentInvoker {
    type InvokeReturn = ResponseObject;

    async fn call_method(
        self,
        param_object: ParamObject,
        data_object: Option<DataObject>, /* custom_object, addon_object */
    ) -> Result<Self::InvokeReturn> {
        ServiceComponentManager::sync_run_service(param_object, data_object).await
    }
}

pub struct RemoteInvoker {
    pub(crate) eai_server: String,
    pub(crate) active_invoker_name: String,
}

#[async_trait]
impl Invoker for RemoteInvoker {
    type InvokeReturn = ResponseObject;

    async fn call_method(
        self,
        mut param_object: ParamObject,
        data_object: Option<DataObject>, /* custom_object, addon_object */
    ) -> Result<Self::InvokeReturn> {
        if let Some(eai_server) = EaiServer::get_eai_server(&self.eai_server) {
            // TODO: doGrayReleaseConfig

            let uri = eai_server.get_eai_server_uri();

            if !eai_server.db_name.is_empty() {
                param_object.set_value_by_env_name("DataBaseName", eai_server.db_name.clone());
            }

            if !eai_server.db_no.is_empty() {
                param_object.set_value_by_env_name("DBNO", eai_server.db_no.clone());
            }

            // if !eai_server.server_product.is_empty() {
            //     param_object
            //         .set_value_by_env_name("Product", Some(eai_server.server_product.clone()));
            // }

            // TODO: generate header_map use eai_server.header

            // TODO: appid and appkey

            InvokerClient::remote_call(uri, self.active_invoker_name, param_object, data_object)
                .await
        } else {
            Err(ErrorCode::EaiServerNotFound(format!(
                "can not find eai_server for {}",
                self.eai_server
            )))
        }
    }
}
